//! Context switch
//!
//!   fn swtch(old: *Context, new: *Context) void;
//!
//! Save current registers in old. Load from new.

const std = @import("std");
const os = @import("../os.zig");

/// Saved registers for kernel context switches.
pub const Context = extern struct {
    /// Return address for context resume - points to the instruction to execute after switch
    /// This is not a normal function call return but a bare context switch entry point
    /// Uses C calling convention for ABI compatibility with context switch code
    ra: *const fn () callconv(.c) noreturn = undefined,
    /// Stack pointer for the context's stack frame
    sp: usize = 0,

    // Callee-saved registers that must be preserved across context switches
    s0: usize = 0,
    s1: usize = 0,
    s2: usize = 0,
    s3: usize = 0,
    s4: usize = 0,
    s5: usize = 0,
    s6: usize = 0,
    s7: usize = 0,
    s8: usize = 0,
    s9: usize = 0,
    s10: usize = 0,
    s11: usize = 0,

    /// Performs a context switch from old to new context
    /// Saves current register state to old context and switches to new context's state
    /// When the switched-from context is later resumed, this function returns to the caller
    pub extern fn swtch(old: *Context, new: *Context) void;
};

const registers = std.meta.tags(std.meta.FieldEnum(Context));

// This function is not marked as noreturn because it will eventually return when the original context is
// later restored, and marking it noreturn would interfere with compiler optimizations at the call site.
export fn swtch() callconv(.naked) void {
    // save current registers to the context pointed to by a0
    inline for (registers, 0..) |register, offset|
        os.arch.op(.save, register, offset * @sizeOf(usize), .a0);

    // restore register state from context pointed by a1
    inline for (registers, 0..) |register, offset|
        os.arch.op(.load, register, offset * @sizeOf(usize), .a1);

    asm volatile ("ret");
}
